#include "Controller.h"

#include "../filehandling/FileType.h"

namespace Server {

Controller::Controller() {
	model = new Model;
}

Controller::~Controller() {
	model->saveAll();
	delete model;
}

void Controller::start() {
	connect(&ncontroller, SIGNAL(newClient(RemoteClient *)), this, SLOT(onNewClient(RemoteClient *)));
	connect(&ncontroller, SIGNAL(clientDied(RemoteClient *)), this, SLOT(onClientDied(RemoteClient *)));
	connect(&ncontroller, SIGNAL(newPacket(RemoteClient *)), this, SLOT(onNewPacket(RemoteClient*)));

	connect(&ncontroller, SIGNAL(newMsg(QString, int)), this, SIGNAL(newMsg(QString, int)));

	ncontroller.start();
}

void Controller::stop() {
	ncontroller.stop();
	ncontroller.wait(3000);
}

Packet * Controller::find(Packet * packet) {
	QString query;
	int type;
	int size_min;
	int size_max;
	ScopedWriter sw("Server::Controller::find()");

	(((*packet >> query) >> type) >> size_min) >> size_max;

	sw << "Zapytanie: " << query.toStdString() << " (" << type << ", <" << size_min << "," << size_max << ">)\n";

	const QVector<FileDesc> files = model->find(query, (Common::FileType)type, size_min, size_max);

	Packet * result = new Packet();
	result->id = Packet::ptList;
	result->client_id = packet->client_id;

	sw << files.size() << " wyników\n";
	*result << files.size();

	foreach (FileDesc fd, files) {
		(*result) << fd.fname[0] << fd.type << fd.size << fd.hash;
	}

	return result;
}

Packet * Controller::findSeeds(Packet * packet) {
	/// @todo implement this
	QByteArray hash;
	int size;

	*packet >> hash >> size;

	std::cout << "Szukam dawców... ";

	const QVector<int> seeds = model->findSeeds(size, hash);
	//std::cout << "zwracam: " << seeds.size() << " [" << seeds[0] << "\n";

	QList<RemoteClient *> tmpList;

	Packet * reply = new Packet;

	reply->id = Packet::ptListSeeds;
	reply->client_id = packet->client_id;

	*reply << hash << size;

	RemoteClient * client;
	foreach (int id, seeds) {
		// nie zwracamy klientowi jego własnego adresu
		if (id == packet->client_id)
			continue;

		// sprawdzamy, czy dany klient jest podłączony
		if (clients_.find(id) == clients_.end())
			continue;

		client = clients_.find(id).value();
		if (client)
			tmpList.push_back(client);
	}


	std::cout << tmpList.size() << "\n";

	*reply << tmpList.size();
	foreach(client, tmpList) {
		*reply << client->id() << client->getHostAddress().toIPv4Address() << client->listeningAtPort();
	}

	return reply;
}

Packet * Controller::idRequest(RemoteClient * client, Packet * packet) {
	Q_UNUSED(packet);

	ScopedWriter sw("Controller::idRequest()");
	Packet * reply = new Packet;
	int id = model->registerUser();

	sw << "";
	reply->client_id = id;
	reply->id = Packet::ptNewID;
	*reply << id;

	client->id(id);

	sw << "Nadane nowe ID: " << id << "\n";

	return reply;
}

Packet * Controller::login(RemoteClient * client, Packet * packet) {
	int client_id;
	int listening_port;

	client_id = packet->client_id;

	Packet * reply = new Packet;
	reply->id = Packet::ptInfo;
	reply->client_id = packet->client_id;

	// jeśli klient o danym id jest już zalogowany to odmawiamy logowania
	// i zrywamy połączenie
	if (clients_.find(client_id) != clients_.end()) {
		*reply << ecAlreadyLoggedIn;
		std::cout << "Klient " << client_id << " jest już zalogowany!\n";
	} else
	// nie istnieje w ogóle taki klient
	if (model->findUser(client_id) == -1) {
		*reply << ecUnknownClient;
		std::cout << "Klient " << client_id << " nie istnieje w bazie danych!\n";
	} else {
		client->id(client_id);
		*packet >> listening_port;
		client->listeningAtPort(listening_port);
		clients_.insert(client_id, client);
		*reply << ecOK;

		std::cout << "Klient " << client_id << " zalogowany pomyślnie [";
		std::cout << client->getHostAddress().toString().toStdString() << ":" << client->listeningAtPort() << "]\n";
	}

	return reply;
}

Packet * Controller::logout(RemoteClient * client, Packet * packet) {
	Q_UNUSED(packet);

	clients_.remove(client->id());
	return NULL;
}

/*
  int count
  --
  int op //
  string fname
  char * hash
  int size
  int type
  */
Packet * Controller::fileList(Packet * packet) {
	int count;
	QString fname;
	QByteArray hash;
	int size;
	int type;
	int state;

	*packet >> count;

	std::cout << count << " plików w paczce od klienta\n";
	std::cout.flush();

	for (int i=0; i < count; ++i) {
		*packet >> state >> fname >> hash >> size >> type;
		switch ( (Common::FileState)state ) {
			case Common::fsNew:
				std::cout << "Dodaję plik " << fname.toStdString() << "\n";
				std::cout.flush();
				model->addFile(fname, size, hash, (Common::FileType)type, packet->client_id);
				break;
			case Common::fsEdit:
				model->removeFile(fname, size, hash, packet->client_id);
				model->addFile(fname, size, hash, (Common::FileType)type, packet->client_id);
				break;
			case Common::fsRemove:
				model->removeFile(fname, size, hash, packet->client_id);
				break;
		}
	}

	return NULL;
}

void Controller::onNewPacket(RemoteClient * client) {
	Packet * packet = client->recv();
	Packet * reply = NULL;

	packet->unpack();
	packet->seek(0, 0);

	switch (packet->id) {
		case Packet::ptFind:
			reply = find(packet);
			break;

		case Packet::ptFindSeeds:
			reply = findSeeds(packet);
			break;

		case Packet::ptIDRequest:
			reply = idRequest(client, packet);
			break;

		case Packet::ptLogin:
			reply = login(client, packet);
			break;

		case Packet::ptLogout:
			reply = logout(client, packet);
			break;

		case Packet::ptFileList:
			reply = fileList(packet);

		default:
			break;
	}

	emit newMsg(packet->dump(), Logger::mtInfo);

	if (reply != NULL) {
		emit newMsg(reply->dump(), Logger::mtInfo);
		client->send(reply);
	}

	delete packet;
}

void Controller::onNewClient(RemoteClient * client) {
	/// @todo imlement this

	emit newMsg(client->getAddr(), 0);
}

void Controller::onClientDied(RemoteClient * client) {
	std::cout << "Połączenie z klientem " << client->id() << " zostało zerwane!\n";
	clients_.remove(client->id());
}

} // namespace Server
